package com.protectsoft.webviewcode;

import ohos.agp.components.webengine.WebConfig;
import ohos.agp.components.webengine.WebView;
import ohos.app.Context;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

/**
 * Codeview
 *
 * @since 2021-04-30
 */
public class Codeview {
    static final String DEFAULT_STYLE = Settings.WithStyle.DEFAULT;
    static final String DEFAULT_LANG = Settings.Lang.JAVA;
    /**
     * NUM_2
     */
    private static final int NUM_2 = 2;
    /**
     * NUM_3
     */
    private static final int NUM_3 = 3;
    /**
     * NUM_4
     */
    private static final int NUM_4 = 4;
    private static volatile Codeview singleton = null;
    private static String[] htmlWrapper = new String[]{"","","","",""};;
    private static List<Content> contents = new ArrayList<>();;
    private final Context mContext;
    private boolean isTextWrap = false;
    private String style = DEFAULT_STYLE;
    private String language = DEFAULT_LANG;;
    private Object mWebview;
    /**
     * Codeview
     *
     * @param context context
     */
    public Codeview(Context context) {
        mContext = context;
    }

    /**
     * Double-checked locking Singleton
     *
     * @param context context
     * @return Codeview
     */
    public static Codeview with(Context context) {
        resetCache();
        if (singleton == null) {
            synchronized (Codeview.class) {
                if (singleton == null) {
                    singleton = new Builder(context).build();
                }
            }
        }
        return singleton;
    }

    /**
     * withCode
     *
     * @param code the code will be highlight
     * @return Codeview
     */
    public Codeview withCode(String code) {
        appender(new Content(code,Content.TYPE_CODE));
        return this;
    }

    /**
     * withText
     *
     * @param text will be treated as simple text no highlight
     * @return Codeview
     */
    public Codeview withText(String text) {
        appender(new Content(text,Content.TYPE_TEXT));
        return this;
    }

    /**
     * withHtml
     *
     * @param html will add html code right after ,can call multiple times to append extra html code into body section
     * @return Codeview
     */
    public Codeview withHtml(String html) {
        appender(new Content(html,Content.TYPE_HTML));
        return this;
    }

    /**
     * setHtmlHeadContent
     *
     * @param htmlHeadContent set link,css,javascript tags inside
     * @return Codeview
     */
    public Codeview setHtmlHeadContent(String htmlHeadContent) {
        htmlWrapper[1] += htmlHeadContent + " \n";
        return this;
    }

    /**
     * setStyle
     *
     * @param style set the highlight style for the code if style is null or empty Default style will be applied.
     * @return Codeview
     * @see Settings.WithStyle
     */
    public Codeview setStyle(String style) {
        if (style != null && !style.isEmpty()) {
            this.style = style;
        } else {
            this.style = DEFAULT_STYLE;
        }
        return this;
    }

    /**
     * setLang
     *
     * @param lang lang
     * @return Codeview
     */
    public Codeview setLang(String lang) {
        if (lang != null && !lang.isEmpty()) {
            this.language = lang;
        } else {
            this.language = DEFAULT_LANG;
        }

        return this;
    }

    /**
     * setAutoWrap
     *
     * @param isWrap wrap
     * @return Codeview
     */
    public Codeview setAutoWrap(boolean isWrap) {
        this.isTextWrap = isWrap;
        return this;
    }

    /**
     * into WebView
     *
     * @param webview or any subclass of webview
     * @param <T> T
     * @throws IllegalArgumentException IllegalArgumentException
     */
    public <T extends WebView> void into(T webview) {
        if (webview == null) {
            throw new IllegalArgumentException("webview cannot be null");
        }

        if (!webview.getWebConfig().isJavaScriptPermitted()) {
            webview.getWebConfig().setJavaScriptPermit(true);
        }

        String htmlDocument = Builder.makeDocument(htmlWrapper,
                new Options(this.isTextWrap, this.style, this.language, contents));
        webview.load(htmlDocument,
                "text/html", "utf-8", "", null);
    }

    /**
     * getWebConfig
     *
     * @return WebConfig
     */
    public WebConfig getWebConfig() {
        return ((WebView)mWebview).getWebConfig();
    }

    private void appender(Content con) {
        contents.add(con);
    }

    /**
     * Builder
     *
     * @since 2021-04-30
     */
    private static class Builder {
        private final Context context;

        /**
         * Builder
         *
         * @param context context
         */
        Builder(Context context) {
            if (context == null) {
                throw new IllegalArgumentException("Context can't be null");
            }
            this.context = context.getApplicationContext();
        }

        /**
         * build
         *
         * @return Codeview
         */
        public Codeview build() {
            return new Codeview(this.context);
        }

        /**
         * makeDocument
         *
         * @param content the array string containing the code,text and headers
         * @param options the users options
         * @return the complete html document
         */
        static String makeDocument(String[] content, Options options) {
            content[0] = HtmlCode.HTML_HEAD + "\n";
            content[1] = content[1] + "<style>" + options.style + "</style> \n";
            content[1] = content[1] + "<script>"
                    + singleton.mContext.getString(ResourceTable.String_highlight_js) + "</script> \n";
            if (options.isTextWrap) {
                content[1] = content[1] + "<style>" + HtmlCode.STYLE_TEXT_WRAP + "</style> \n";
            }
            content[1] = content[1] + "<script>" + "hljs.initHighlightingOnLoad();" + "</script> \n";
            content[NUM_2] = HtmlCode.HTML_HEAD_END + "\n";
            content[NUM_3] += extractBuildContent(options.contents,options.isTextWrap,options.lang);
            content[NUM_4] = HtmlCode.HTML_BODY_END + " \n";
            return content[0] + content[1] + content[NUM_2] + content[NUM_3] + content[NUM_4];
        }
    }

    /**
     * Options
     *
     * @since 2021-04-30
     */
    private static class Options {
        private final boolean isTextWrap;
        private final String style;
        private final String lang;
        private final List<Content> contents;

        /**
         * Options
         *
         * @param isTextWrap isTextWrap
         * @param style style
         * @param lang lang
         * @param contents contents
         */
        Options(boolean isTextWrap,String style,String lang,List<Content> contents) {
            this.isTextWrap = isTextWrap;
            this.style = style;
            this.lang = lang;
            this.contents = contents;
        }
    }

    /**
     * extractBuildContent
     *
     * @param contentList contentList
     * @param isTextWrap isTextWrap
     * @param language language
     * @return String
     */
    private static String extractBuildContent(List<Content> contentList,boolean isTextWrap,String language) {
        if (!contentList.isEmpty()) {
            StringBuilder content = new StringBuilder();
            for (Content value : contentList) {
                if (value.getType().equals(Content.TYPE_CODE)) {
                    if (isTextWrap) {
                        content.append(HtmlCode.HTML_PRE_CODE_WRAP_START)
                                .append(language).append(HtmlCode.HTML_PRE_CODE_CLASS)
                                .append(value.getText()).append(HtmlCode.HTML_PRE_CODE_END);
                    } else {
                        content.append(HtmlCode.HTML_PRE_CODE_START)
                                .append(language).append(HtmlCode.HTML_PRE_CODE_CLASS)
                                .append(value.getText()).append(HtmlCode.HTML_PRE_CODE_END);
                    }
                } else if (value.getType().equals(Content.TYPE_HTML)) {
                    content.append(value.getText());
                } else {
                    content.append(HtmlCode.HTML_PRE_TEXT_START)
                            .append(value.getText()).append(HtmlCode.HTML_PRE_TEXT_END);
                }
            }
            return content.toString();
        }
        return "";
    }

    private <T extends WebView> void checkWebview(T checkWebview) {
        checkWebview.load("about:blank", "text/html", "utf-8", "", null);
    }

    private static void resetCache() {
        contents = new ArrayList<Content>();
        htmlWrapper = new String[]{"","","","",""};
    }

    /**
     * getAllCodeStyles
     *
     * @return List
     * @see Settings.WithStyle
     */
    static List<String> getAllCodeStyles() {
        Field[] fields = Settings.WithStyle.class.getFields();
        List<String> styles = new ArrayList<String>();
        for (Field filed : fields) {
            styles.add(filed.getName());
        }
        return styles;
    }

    /**
     * getAllLanguages
     *
     * @return List
     */
    static List<String> getAllLanguages() {
        Field[] fields = Settings.Lang.class.getFields();
        List<String> langs = new ArrayList<String>();
        for (Field filed : fields) {
            langs.add(filed.getName());
        }
        return langs;
    }
}
